﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ReportingServices.DeploymentCenter.Core.Logging;
using ReportingServices.DeploymentCenter.Core.ReportingServicesServiceReference;

namespace ReportingServices.DeploymentCenter.Core
{
    public class DeploymentManager : IDisposable
    {
        #region Fields

        private bool _disposed = false;
        private TrustedUserHeader _trustedHeader;

        #endregion

        #region Properties

        private ServiceConfiguration ReportingServicesServiceConfiguration { get; set; }

        internal TrustedUserHeader TrustedHeader
        {
            get
            {
                if (_trustedHeader == null)
                {
                    if (System.Security.Principal.WindowsIdentity.GetCurrent() != null)
                    {
                        _trustedHeader = new TrustedUserHeader() { UserName = System.Security.Principal.WindowsIdentity.GetCurrent().Name };
                    }
                }

                return _trustedHeader;
            }
        }

        #endregion

        #region Constructors

        public DeploymentManager(ServiceConfiguration serviceConfiguration)
        {
            ReportingServicesServiceConfiguration = serviceConfiguration;
        }

        #endregion

        #region Methods

        public void CreateFolder(string path)
        {
            string[] pathParts = path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);

            CatalogItem[] serverFolders = GetReportServerFolders("/", true);
            List<string> serverPaths = serverFolders.Select(i => i.Path).ToList();

            if (!serverPaths.Contains(path))
            {
                string folderPath = String.Empty;

                foreach (string part in pathParts)
                {
                    folderPath += String.Format("/{0}", part);

                    if (!serverPaths.Contains(folderPath))
                    {
                        string parentFolder = folderPath.Substring(0, folderPath.LastIndexOf("/"));
                        string folderName = folderPath.Substring(folderPath.LastIndexOf("/") + 1);

                        CreateFolder(folderName, parentFolder);
                    }
                }
            }
        }

        public CatalogItem CreateFolder(string name, string parentFolder)
        {
            CatalogItem catalogItem = null;

            ExecuteRequest(query =>
            {
                query.CreateFolder(TrustedHeader, name, parentFolder, null, out catalogItem);
            });

            return catalogItem;
        }

        private NamedDataSource DataSourceFromRds(string rdsPath)
        {
            System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
            doc.Load(rdsPath);

            DataSourceDefinition dataSource = new DataSourceDefinition();
            dataSource.ConnectString = doc.SelectSingleNode("/RptDataSource/ConnectionProperties/ConnectString/text()").Value;
            dataSource.Extension = doc.SelectSingleNode("/RptDataSource/ConnectionProperties/Extension/text()").Value;
            dataSource.CredentialRetrieval = CredentialRetrievalEnum.Integrated;
            dataSource.Enabled = true;
            dataSource.EnabledSpecified = true;

            NamedDataSource result = new NamedDataSource();
            result.Definition = dataSource;
            result.Name = doc.SelectSingleNode("/RptDataSource").Attributes["Name"].Value;

            return result;
        }

        public List<DeploymentResult> DeployFiles(string deploymentFolder, string[] filePaths, bool overwriteFiles)
        {
            List<DeploymentResult> results = new List<DeploymentResult>();

            List<string> dataSourcePaths = filePaths.Where(item => item.Contains(".rds")).ToList();
            List<string> reportPaths = filePaths.Where(item => item.Contains(".rdl")).ToList();

            List<string> orderedPaths = dataSourcePaths.Union(reportPaths).ToList();

            foreach (string fileName in orderedPaths)
            {
                string itemType = String.Empty;

                if (fileName.Contains(".rds"))
                    itemType = "DataSource";
                else if (fileName.Contains(".rdl"))
                    itemType = "Report";

                DeploymentResult deploymentResult = new DeploymentResult();
                string itemName = Path.GetFileNameWithoutExtension(fileName);
                Byte[] reportDefinition = null;
                CatalogItem itemInfo = null;
                Warning[] warnings = null;

                using (FileStream stream = File.OpenRead(fileName))
                {
                    reportDefinition = new Byte[stream.Length];
                    stream.Read(reportDefinition, 0, (int)stream.Length);
                }

                ExecuteRequest(query =>
                {
                    if (fileName.Contains(".rds"))
                    {
                        NamedDataSource dataSource = DataSourceFromRds(fileName);
                        query.CreateDataSource(TrustedHeader, dataSource.Name, deploymentFolder, overwriteFiles, dataSource.Definition, null, out itemInfo);
                    }
                    else if (fileName.Contains(".rdl"))
                        query.CreateCatalogItem(TrustedHeader, itemType, itemName, deploymentFolder, overwriteFiles, reportDefinition, null, out itemInfo, out warnings);
                });

                if (itemInfo != null)
                    deploymentResult.ItemInformation = itemInfo;

                if (warnings != null)
                    deploymentResult.Warnings = warnings;

                results.Add(deploymentResult);

                ItemDeployedEventArgs args = new ItemDeployedEventArgs(deploymentResult);
                ItemDeployed(args);
            }

            return results;
        }

        public void Dispose()
        {
            // Dispose of unmanaged resources.
            Dispose(true);

            // Suppress finalization.
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
                return;

            if (disposing)
            {
                // Free any other managed objects here. 
            }

            // Free any unmanaged objects here. 
            _disposed = true;
        }

        private void ExecuteRequest(Action<ReportingService2010SoapClient> query)
        {
            try
            {
                using (ReportingService2010SoapClient reportingServicesClient = new ReportingService2010SoapClient("ReportingService2010Soap", ReportingServicesServiceConfiguration.ReportServerUrl))
                {
                    reportingServicesClient.ClientCredentials.Windows.ClientCredential.UserName = ReportingServicesServiceConfiguration.UserName;
                    reportingServicesClient.ClientCredentials.Windows.ClientCredential.Password = ReportingServicesServiceConfiguration.Password;
                    reportingServicesClient.ClientCredentials.Windows.ClientCredential.Domain = ReportingServicesServiceConfiguration.Domain;
                    reportingServicesClient.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;

                    query.Invoke(reportingServicesClient);
                }
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);

                DeploymentResult result = new DeploymentResult();
                result.Warnings = new Warning[] { new Warning() { Message = ex.Message} };

                ItemDeployedEventArgs args = new ItemDeployedEventArgs(result);
                ItemDeployed(args);                
            }
        }

        public CatalogItem[] GetReportServerFolders(string folderPath, bool recursive)
        {
            CatalogItem[] catalogItems = null;

            ExecuteRequest(query =>
            {
                query.ListChildren(TrustedHeader, folderPath, recursive, out catalogItems);
            });

            return catalogItems;
        }

        protected void ItemDeployed(ItemDeployedEventArgs itemDeploymentEventArgs)
        {
            EventHandler<ItemDeployedEventArgs> handler = OnItemDeployed;

            if (handler != null)
                handler(this, itemDeploymentEventArgs);
        }

        #endregion

        #region Events

        public event EventHandler<ItemDeployedEventArgs> OnItemDeployed;

        #endregion
    }
}